#ifndef __MULTILAYER_PERCEPTRON_H__
#define __MULTILAYER_PERCEPTRON_H__

#include "Model.h"
#include "ErrorFunction.h"
#include "../ClMem.h"
#include "../BindKernelArgs.h"

namespace learn {

struct Neuron {
	static const float MAX_INPUT;
	
	int weightStartIndex;
	int inputStartIndex;
	int weightCount;
	int outputIndex;
};

struct Layer {	
	int neuronStartIndex;
	int neuronEndIndex;
};

struct MultilayerPerceptron : Model, ErrorFunction {
	
	struct Builder {
		Builder& setInputCount(int count);		
		Builder& addHiddenLayer(int neuronCount);
		MultilayerPerceptron* create(cl_command_queue queue);
	private:
		int inputCount;
		std::vector<int> layerSizes;
	};
	friend struct Builder;
	
	virtual ~MultilayerPerceptron();
	
	virtual int getParameterCount();
	virtual int getInputCount();
	virtual int getOutputCount();
	virtual void setParameters(float *values, int offset, int count);
	virtual void getParameters(float *outValues, int offset, int count);
	
	// Model
	virtual void run(float *input, float *output);
	virtual void getDerivative(float *input, float *outDerivative);
	
	// ErrorFunction
	virtual float getError(Model &model, OptimizationSupervisor &data);
	virtual float getErrorDerivative(Model& model, OptimizationSupervisor &data, float *outDerivative);
	
protected:	
	void activate(float *input);
	
private:
	int inputCount;	
	std::vector<Layer> layers;
	std::vector<Neuron> hostNeurons;
	
    cl_command_queue queue;    
    ClMem<Neuron> deviceNeurons;	
    ClMem<float> data;
    ClMem<float> weights;
    ClMem<float> neuronSumms;
    cl_kernel activateNeuron;
	cl_kernel calculateError;
	cl_kernel addError;
	cl_kernel scale;
	cl_kernel scaleErrorByDerivative;
	cl_kernel calculateErrorGradient;
    
	MultilayerPerceptron(cl_command_queue _queue);
    
    ClEvent activateLayerAsync(Layer layer);
	ClEvent backpropagateErrorAsync(Layer prevLayer, int neuronIndex, ClMem<float> errors);
	ClEvent scaleAsync(float factor, ClMem<float> v, size_t offsetV, size_t count);
	ClEvent scaleErrorsByDerivativeAsync(Layer layer, ClMem<float> errors);
	ClEvent calculateErrorGradientAsync(int neuronIndex, ClMem<float> errors, ClMem<float> derivatives);
};

}
 
#endif // __MULTILAYER_PERCEPTRON_H__